#include "sc/context.h"
#include "sc/interface.h"
#include "sc/xml.h"

#include "boost/algorithm/string/erase.hpp"

#include "boost/filesystem/operations.hpp"
#include "boost/filesystem/path.hpp"
#include "boost/regex.hpp"
#include "boost/foreach.hpp"

#include "utils/path_util.hpp"

#include <sstream>

#include "utils\traceur.h"

#define foreach BOOST_FOREACH

using namespace sc;


/**
* Clear the context.
*  Remove all information about it,
*  Remove setup.
*/
void Context::Clear(void)
{
    foreach (SetupContext* pSetupCtx, m_setupContexts)
        delete pSetupCtx;
    m_setupContexts.clear();

    if (m_sccDoc)
    {
        xmlFreeDoc(m_sccDoc);
        m_sccDoc = NULL;
    }
}

/**
* Set the context file name
*
* @param sFileRNG
*
* @return true if the RNG file exists and is valid
*/
bool Context::SetFile(const std::string& sFileSCC)
{
    m_sFilename = sFileSCC;
    return true;
}

/**
* Set the title of the project
*
* @param sTitle
*
* @return true
*/
 bool Context::SetTitle(const std::string& sTitle)
 {
     m_sTitle = sTitle;
     return true;
 }

/**
* Load a context
*
* @param sFilenameSCC SCC file name
*
* @return loading error
*/
int Context::Load(const std::string& sFilenameSCC)
{
    int error = SCE_SHEMA_INVALID;

    m_sFilename = sFilenameSCC;

    // Load the SCC document
    m_sccDoc = xmlReadFile(sFilenameSCC.c_str(), NULL, 0);
    if (m_sccDoc == NULL)
        return SCE_SHEMA_INVALID;

    xmlNodePtr root = xmlDocGetRootElement(m_sccDoc);

Traceur::Printf("Context::Load(%s) xml_version=%s, xml_encoding=%s", sFilenameSCC.c_str(), m_sccDoc->version, m_sccDoc->encoding);

    if (root && !xmlStrcmp(root->name, xml::scc::_context))
    {
        error = SCE_NONE;

        // Load the title
        const xmlChar* sTitle = xmlGetProp(root, xml::scc::_title);
        if (sTitle)
        {
            m_sTitle = (const char*) sTitle;
        }

        // Load the children
        xmlNodePtr child = root->children;
        while (child && !error)
        {
            if (child->type == XML_ELEMENT_NODE)
            {
                if (!xmlStrcmp(child->name, xml::scc::_file))
                {
                    SetupContext* pSetupCtx = new SetupContext(m_sFilename);
                    pSetupCtx->Load(child);
                    m_setupContexts.push_back(pSetupCtx);
                }
            }
            child = child->next;
        }
    }
    return error;
}

/**
* Save all setup file
*
* @return saving error
*/
int Context::SaveSetups(void) const
{
    int error = SCE_NONE;
    TSetupContexts::const_iterator iter;
    for (iter = m_setupContexts.begin(); iter != m_setupContexts.end() && !error; ++iter)
    {
        error = (*iter)->SaveSetup();
    }
    return error;
}

/**
* Return the file name relative to the context path
*
* @param sFile
*
* @return the relative path for sFile
*/
std::string Context::FileRelativeToContext(const std::string& sFile) const
{
    return m_sFilename.empty()
         ? sFile
         : boost::filesystem::relative_to( boost::filesystem::path(m_sFilename)
                                         , boost::filesystem::path(sFile)
                                         ).string();
}




/**
* Clear the context.
*  Remove all information about it,
*  Remove setup.
*/
void SetupContext::Clear(void)
{
    m_setup.Clear();
    m_MmiElements.clear();
    m_MmiAttributes.clear();
}

/**
* Load the context setup
*
* @param pNodeFile
*
* @return loading error
*/
int SetupContext::Load(const xmlNodePtr pNodeFile)
{
    int error = SCE_SHEMA_INVALID;

    const xmlChar* sXmlFile = xmlGetProp(pNodeFile, xml::scc::_setup);
    const xmlChar* sRngFile = xmlGetProp(pNodeFile, xml::scc::_rng);
    if (sXmlFile && sRngFile)
    {
        m_sFileXML = (const char*) sXmlFile;

        if (SetConfigFile((const char*) sRngFile))
        {
            boost::filesystem::path pathSCC(*m_psFilename);

            // Load Config
            boost::filesystem::path pathRNG(pathSCC.branch_path() / m_sFileRNG);
            error = m_setup.LoadConfig(pathRNG.string());

            if (!error)
            {
                // Load Setup (xml file)
                boost::filesystem::path pathXML(pathSCC.branch_path() / m_sFileXML);
                if (boost::filesystem::exists(pathXML))
                {
                    error = m_setup.Load(pathXML.string());
                }
            }
        }

        xmlNodePtr child = pNodeFile->children;
        while (child && !error)
        {
            if (child->type == XML_ELEMENT_NODE)
            {
                if (!xmlStrcmp(child->name, xml::scc::_mmi))
                {
                    error = LoadMMI(child);
                }
            }
            child = child->next;
        }
    }
    return error;
}

/**
* Load the MMI context
*  - the way to print label
*  - icon for the tree item
*
* @param pNodeMMI
*
* @return loading error
*/
int SetupContext::LoadMMI(const xmlNodePtr pNodeMMI)
{
    int error = SCE_NONE;

    xmlNodePtr child = pNodeMMI->children;
    while (child && !error)
    {
        if (child->type == XML_ELEMENT_NODE)
        {
            if (!xmlStrcmp(child->name, xml::scc::_element_label))
            {
                // Element label
                error = LoadElementLabel(child);
            }
            else if (!xmlStrcmp(child->name, xml::scc::_attribute_label))
            {
                // Attribute label
                error = LoadAttributeLabel(child);
            }
        }
        child = child->next;
    }
    return error;
}

/**
* Load the label of an Element
*
* @param pNodeLabel
*
* @return loading error
*/
int SetupContext::LoadElementLabel(const xmlNodePtr pNodeLabel)
{
    int error = SCE_SHEMA_INVALID;

    const xmlChar* sFor = xmlGetProp(pNodeLabel, xml::scc::_for);
    if (sFor != NULL)
    {
        error = SCE_NONE;

        std::string sElement( (const char*) sFor );

        // Load the label value
        const xmlChar* sValue   = xmlGetProp(pNodeLabel, xml::scc::_value);
        if (sValue)
        {
            m_MmiElements[sElement].sValue = (const char*) sValue;
        }

        // load the default label value (if no value exist)
        const xmlChar* sDefault = xmlGetProp(pNodeLabel, xml::scc::_default);
        if (sDefault)
        {
            // Load the label value
            m_MmiElements[sElement].sDefault = (const char*) sDefault;
        }

        // Icon
        const xmlChar* sIcon    = xmlGetProp(pNodeLabel, xml::scc::_icon);
        if (sIcon)
        {
            // Load the icon path
            boost::filesystem::path pathSCC(*m_psFilename);
            boost::filesystem::path pathICO(pathSCC.branch_path() / (const char*) sIcon);
            if (boost::filesystem::exists(pathICO))
            {
                m_MmiElements[sElement].sIcon = pathICO.native_file_string();
            }
        }
    }
    return error;
}

/**
* Load the label of an Attribute
*
* @param pNodeLabel
*
* @return loading error
*/
int SetupContext::LoadAttributeLabel(const xmlNodePtr pNodeLabel)
{
    int error = SCE_SHEMA_INVALID;

    const xmlChar* sFor   = xmlGetProp(pNodeLabel, xml::scc::_for);
    const xmlChar* sValue = xmlGetProp(pNodeLabel, xml::scc::_value);
    if (sFor && sValue)
    {
        m_MmiAttributes[(const char*)sFor].sValue = (const char*) sValue;

        const xmlChar* sError = xmlGetProp(pNodeLabel, xml::scc::_error);
        if (sError)
        {
            m_MmiAttributes[(const char*)sFor].sError = xml::UTF8ToStr(sError);

Traceur::Printf("SetupContext::LoadAttributeLabel error=%s", sError, m_MmiAttributes[(const char*)sFor].sError.c_str());
        }

        const xmlChar* sHelp = xmlGetProp(pNodeLabel, xml::scc::_help);
        if (sHelp)
        {
            m_MmiAttributes[(const char*)sFor].sHelp = xml::UTF8ToStr(sHelp);

Traceur::Printf("SetupContext::LoadAttributeLabel help=%s", sHelp, m_MmiAttributes[(const char*)sFor].sHelp.c_str());
        }
        error = SCE_NONE;
    }
    return error;
}

#if 0
/**
* Save the current context to the same file as before
*
* @return saving error
*/
int SetupContext::Save(void) const
{
    int error = SCE_SCC_NOT_LOADED;

    if (!m_sFilename.empty())
    {
        error = SCE_CANT_SAVE_FILE;

        // Create the context doc writer
        xmlTextWriterPtr xmlWriter = xmlNewTextWriterFilename(m_sFilename.c_str(), 0);
        if (xmlWriter && !xmlTextWriterSetIndent(xmlWriter, 2))
        {
            // Add the xml root node
            if ( xmlTextWriterStartDocument(xmlWriter, NULL, NULL, NULL) >= 0
              && xmlTextWriterStartElement(xmlWriter, xml::scc::_context) >= 0
              && xmlTextWriterWriteAttribute(xmlWriter, xml::scc::_title, (const xmlChar*) m_sTitle.c_str()) >= 0 )
            {
                error = SaveConfig(xmlWriter);
                if (!error)
                    error = SaveSetup(xmlWriter);
                if (!error)
                    error = SaveMMI(xmlWriter);

                // End document
                xmlTextWriterEndElement(xmlWriter);
                xmlTextWriterEndDocument(xmlWriter);
            }
            xmlFreeTextWriter(xmlWriter);
        }
        error = SCE_NONE;
    }
    return error;
}

/**
* Save the context to the specified file name
*
* @param sFilenameSCC
*
* @return saving error
*/
int SetupContext::Save(const std::string& sFilenameSCC)
{
    m_sFilename = sFilenameSCC;
    return Save();
}
#endif // 0

/**
* Save the context file to the current scc file name
*
* @param pNodeScc
*
* @return saving error
*/
int SetupContext::Save(xmlTextWriterPtr xmlWriter) const
{
    int error = SCE_CANT_SAVE_FILE;

    error = SaveConfig(xmlWriter);
    error = error ? error : SaveSetup(xmlWriter);
    error = error ? error : SaveMMI(xmlWriter);

    return error;
}

/**
* Save the setup
*
* @return saving error
*/
int SetupContext::SaveSetup(void) const
{
    int error = SCE_CONTEXT_DONT_HAVE_SETUP;
    if (!m_sFileXML.empty())
    {
        boost::filesystem::path pathSCC(*m_psFilename);
        boost::filesystem::path pathSetup(pathSCC.branch_path() / m_sFileXML);
        error = m_setup.Save(pathSetup.native_file_string());
    }
    return error;
}

/**
* Save the configuration file in the context
*
* @param xmlWriter
*
* @return saving error
*/
int SetupContext::SaveConfig(xmlTextWriterPtr xmlWriter) const
{
    int error = SCE_CANT_SAVE_FILE;

	// Add the xml node with the name of the element
	if ( xmlTextWriterStartElement(xmlWriter, xml::scc::_rng) >= 0
	  && xmlTextWriterWriteAttribute(xmlWriter, xml::scc::_path, (xmlChar*) "test.rng") >= 0 )
    {
	    xmlTextWriterEndElement(xmlWriter);
		error = SCE_NONE;
	}
    return error;
}

/**
* Save the setup file name in the context
*
* @param xmlWriter
*
* @return saving error
*/
int SetupContext::SaveSetup(xmlTextWriterPtr xmlWriter) const
{
    int error = SCE_CANT_SAVE_FILE;

	// Add the xml node with the name of the element
	if ( xmlTextWriterStartElement(xmlWriter, xml::scc::_setup) >= 0
	  && xmlTextWriterWriteAttribute(xmlWriter, xml::scc::_path, (xmlChar*) "test.xml") >= 0 )
    {
	    xmlTextWriterEndElement(xmlWriter);
//	    error = m_setup.Save(m_sFileXML);
		error = SCE_NONE;
	}
    return error;
}

/**
* Save the MMI context, ie : label and icons
*
* @param xmlWriter
*
* @return saving error
*/
int SetupContext::SaveMMI(xmlTextWriterPtr xmlWriter) const
{
	int error = SCE_CANT_SAVE_FILE;
/**
	// Add the xml node with the name of the element
	if (xmlTextWriterStartElement(xmlWriter, xml::scc::_mmi) >= 0)
	{
		error = SCE_NONE;

        // Save all MMI Element informations
        foreach (const TMmiElements::value_type& rcMmiElement, m_MmiElements)
        {
            if (xmlTextWriterStartElement(xmlWriter, xml::scc::_element_label) < 0)
            {
                error = SCE_CANT_SAVE_FILE;
            }
            else
            {
                // Attribute : for
                xmlTextWriterWriteAttribute( xmlWriter
                                           , xml::scc::_for
										   , (const xmlChar*) rcMmiElement.first.c_str()
										   );

                if (!rcMmiElement.second.sValue.empty())
                {
                    // Attribute : value
                    xmlTextWriterWriteAttribute( xmlWriter
                                               , xml::scc::_value
                                               , (const xmlChar*) rcMmiElement.second.sValue.c_str()
                                               );
                }

                if (!rcMmiElement.second.sIcon.empty())
                {
                    // Attribute : icon
                    xmlTextWriterWriteAttribute( xmlWriter
                                               , xml::scc::_icon
                                               , (const xmlChar*) FileRelativeToContext(rcMmiElement.second.sIcon).c_str()
                                               );
                }
                xmlTextWriterEndElement(xmlWriter);
            }
        }

        // Save all MMI Attribute informations
        foreach (const TMmiAttributes::value_type& rcMmiAttribute, m_MmiAttributes)
        {
            if (xmlTextWriterStartElement(xmlWriter, xml::scc::_attribute_label) < 0)
            {
                error = SCE_CANT_SAVE_FILE;
            }
            else
            {
                // Attribute : for
                xmlTextWriterWriteAttribute( xmlWriter
                                           , xml::scc::_for
										   , (const xmlChar*) rcMmiAttribute.first.c_str()
										   );

                if (!rcMmiAttribute.second.sValue.empty())
                {
                    // Attribute : value
                    xmlTextWriterWriteAttribute( xmlWriter
                                               , xml::scc::_value
                                               , (const xmlChar*) rcMmiAttribute.second.sValue.c_str()
                                               );
                }

                if (!rcMmiAttribute.second.sHelp.empty())
                {
                    // Attribute : help
                    xmlTextWriterWriteAttribute( xmlWriter
                                               , xml::scc::_help
                                               , (const xmlChar*) rcMmiAttribute.second.sHelp.c_str()
                                               );
                }
                xmlTextWriterEndElement(xmlWriter);
            }
        }
        xmlTextWriterEndElement(xmlWriter);
	}
*/
	return error;
}

/**
* Get the label of an Element according to the context
*
* @param rcElement element of which we need the label
*
* @return label
*/
std::string SetupContext::GetElementLabel(const setup::Element& rcElement) const
{
    std::string sLabel = rcElement.GetName();
    TMmiElements::const_iterator iter = m_MmiElements.find(sLabel);
    if (iter != m_MmiElements.end())
    {
        sLabel = iter->second.sValue;

        // Label is dynamic ?
        std::string::const_iterator start = sLabel.begin();
        std::string::const_iterator end   = sLabel.end();
        boost::match_results<std::string::const_iterator> what;
        boost::regex re_find("%([a-zA-Z0-9-]*)%");
        boost::match_flag_type flags = boost::match_default;
        while(regex_search(start, end, what, re_find, flags))
        {
            std::string sMatch(what[1].first, what[1].second);

            // Does the attribute exist ?
            setup::TAttributes::const_iterator iterAtt = std::find_if( rcElement.GetAttributes().begin()
                                                                     , rcElement.GetAttributes().end()
                                                                     , setup::PredFindAttributeByName(sMatch)
                                                                     );
            if (iterAtt != rcElement.GetAttributes().end())
            {
                const setup::Attribute& rcAttribute = *iterAtt;
                if (!rcAttribute.m_sValue.empty())
                {
                    // Create the regex to replace dynamic info
                    std::ostringstream oss;
                    oss << "(%" << sMatch << "%)";

                    boost::regex re_replace(oss.str());
                    sLabel = boost::regex_replace(sLabel, re_replace, rcAttribute.m_sValue);
                }
                else
                {
                    // Attribute empty : We remove all to use the default value
                    sLabel.clear();
                    break;
                }
            }
            else
            {
                // Attribute doesn't exist : We remove all to use the default value
                sLabel.clear();
                break;
            }
            // update search position and update flags :
            start = what[0].second;
            flags |= (boost::match_prev_avail | boost::match_not_bob);
        }

        // If label is empty, we fill it with the element name or default value
        if (sLabel.empty())
        {
            if (iter->second.sDefault.empty())
                sLabel = rcElement.GetName();
            else
                sLabel = iter->second.sDefault;
        }
    }
    return sLabel;
}

/**
* Get the label of an Attribute according to the context
*
* @param rcAttribute Attribute of which we need the label
*
* @return label
*/
std::string SetupContext::GetAttributeLabel(const setup::Attribute& rcAttribute) const
{
    std::string sLabel = rcAttribute.m_rcConf.GetName();
    TMmiAttributes::const_iterator iter = m_MmiAttributes.find(sLabel);
    if (iter != m_MmiAttributes.end())
    {
        sLabel = iter->second.sValue;
    }
    return sLabel;
}

/**
* Get the error text of an Attribute according to the context
*
* @param rcAttribute Attribute of which we need the error message
*
* @return label
*/
std::string SetupContext::GetAttributeError(const setup::Attribute& rcAttribute) const
{
    std::string sError;
    TMmiAttributes::const_iterator iter = m_MmiAttributes.find(rcAttribute.m_rcConf.GetName());
    if (iter != m_MmiAttributes.end())
    {
        sError = iter->second.sError;
    }
    return sError;
}

/**
* Get the help text of an Attribute according to the context
*
* @param rcAttribute Attribute of which we need the help message
*
* @return label
*/
std::string SetupContext::GetAttributeHelp(const setup::Attribute& rcAttribute) const
{
    std::string sError;
    TMmiAttributes::const_iterator iter = m_MmiAttributes.find(rcAttribute.m_rcConf.GetName());
    if (iter != m_MmiAttributes.end())
    {
        sError = iter->second.sHelp;
    }
    return sError;
}

/**
* Set the config (Relax NG) file
*
* @param sFileRNG
*
* @return true if the RNG file exists and is valid
*/
bool SetupContext::SetConfigFile(const std::string& sFileRNG)
{
    bool bExist = false;
    boost::filesystem::path pathSCC(*m_psFilename);
    boost::filesystem::path pathRNG(pathSCC.branch_path() / sFileRNG);
    if (boost::filesystem::exists(pathRNG))
    {
        if (Config::IsRngValid(pathRNG.native_file_string()))
        {
            m_sFileRNG = sFileRNG;
            bExist = true;
        }
    }
    return bExist;
}

/**
* Set the Setup (XML) file,
*
* @param sFileXML
* @param bRelative  set to true if you want a relative path to tje context, or false if
*   you want an absolute path.
*
* @return true
*/
bool SetupContext::SetXmlFile(const std::string& sFileXML, bool bRelative)
{
    if (bRelative)
    {
        boost::filesystem::path pathSCC(*m_psFilename);
        boost::filesystem::path passXML(sFileXML);
        m_sFileXML = boost::filesystem::relative_to(pathSCC, passXML).string();
    }
    else
    {
        m_sFileXML = sFileXML;
    }
    return true;
}
